﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="TargetSystemViewModel.cs">
//   J. Prinz
// </copyright>
// <summary>
//   The View Model of the Target System. The Target-System is the Importer of the AutomationML DataObjects which have
//   been exported from the Source-System . A Target-System ViewModel contains
//   Command-Interfaces for Importing AutomationML-Objecs  and Mapping
//   of AutomationML Objects
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace AutomationMLMapper.ViewModels.SystemViewModels
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Reflection;
    using System.Windows.Input;

    using AMLEngineExtensions;

    using CAEX_ClassModel;
    using AutomationMLMapper.AutomationML.Extensions;
    using AutomationMLMapper.Properties;
    using AutomationMLMapper.ViewModels.CAEXObjectCollectionViewModels;
    using AutomationMLMapper.ViewModels.CAEXObjectViewModels;

    using Microsoft.Win32;
    using AutomationMLMapper.MVVM_Framework;

    /// <summary>
    ///   The View Model of the Target System. The Target-System is the Importer of the AutomationML DataObjects which have
    ///   been exported from the Source-System <see cref="SourceSystemViewModel" />. A Target-System ViewModel contains
    ///   Command-Interfaces for Importing AutomationML-Objecs <see cref="ExportInstancesCollection" /> and Mapping
    ///   of AutomationML Objects
    /// </summary>
    public class TargetSystemViewModel : SystemViewModel
    {
        #region Fields

        /// <summary>
        ///  <see cref="LoadIHCommand"/>
        /// </summary>
        private RelayCommand<object> loadIHCommand;

        /// <summary>
        ///  <see cref="mapIECommand"/>
        /// </summary>
        private RelayCommand<object> mapIECommand;

        /// <summary>
        ///   <see cref="mapIEToClassCommand"/>
        /// </summary>
        private RelayCommand<object> mapIEToClassCommand;

        #endregion

        #region Constructors and Destructors

        /// <summary>
        /// Initializes a new instance of the <see cref="TargetSystemViewModel"/> class. 
        /// </summary>
        public TargetSystemViewModel()
        {
            this.ExportInstancesCollection = new ExportInternalElementCollectionViewModel(this)
                {
                   AllowAdd = false, AllowDelete = false, AllowEditElement = false 
                };

            this.CreateEmptyLibrary(Resources.ImportSystem);
        }

        #endregion

        #region Public Properties

        /// <summary>
        ///   Collection of AutomationML-Objects that have been Exported by a Source-System and should be imported 
        ///   into the Target-System
        /// </summary>
        public ExportInternalElementCollectionViewModel ExportInstancesCollection { get; set; }

        /// <summary>
        ///   Load an Instance Hierarchy of AutomationML Objects for Import into the Target-System. The Objects are loaded and
        ///   represented in the <see cref="ExportInstancesCollection" />
        /// </summary>
        public ICommand LoadIHCommand
        {
            get
            {
                return this.loadIHCommand
                       ??
                       (this.loadIHCommand =
                        new RelayCommand<object>(this.LoadIHCommandExecute, LoadIHCommandCanExecute));
            }
        }

        /// <summary>
        ///   Map Command logic for the Mapping of an InternalElement to a SystemUnitClass with its RoleRequirement
        /// </summary>
        public ICommand MapIECommand
        {
            get
            {
                return this.mapIECommand
                       ??
                       (this.mapIECommand =
                        new RelayCommand<object>(this.MapIECommandExecute, this.MapIECommandCanExecute));
            }
        }

        /// <summary>
        ///   Map Command logic for the Mapping of an InternalElement to a SystemUnitClass with its RoleRequirement
        /// </summary>
        public ICommand MapIEToClassCommand
        {
            get
            {
                return this.mapIEToClassCommand
                       ??
                       (this.mapIEToClassCommand =
                        new RelayCommand<object>(this.MapIEToClassCommandExecute, this.MapIEToClassCommandCanExecute));
            }
        }

        #endregion

        #region Methods

        /// <summary>
        ///   Add Mapping Libraries to the AutomationML Document, if they are not already present
        /// </summary>
        /// <returns> The System.Boolean. </returns>
        protected bool AddMappingLibraries()
        {
            var assembly = Assembly.GetExecutingAssembly();

            // add the MappingRoleClassLib
            if (this.AmlDocumentRepresentation.CaexObject.RoleClassLib.FindCaexObjectByName("MappingRoleClassLib")==null)
            {
                var roleClassLibDocument =
                    CAEXDocument.LoadFromStream(
                        assembly.GetManifestResourceStream(
                            "AutomationMLMapper.AutomationML.MappingLibraryResources.MappingRoleClassLib.aml"));
                if (roleClassLibDocument != null)
                {
                    var lib = roleClassLibDocument.CAEXFile.RoleClassLib.GetCaexObjectByName("MappingRoleClassLib");
                    if (lib is RoleClassLibType)
                    {
                        var mappingRoleClassLibrary =
                            this.AmlDocumentRepresentation.CaexObject.Import_RoleClassLibHierarchy(
                                lib as RoleClassLibType);
                        if (mappingRoleClassLibrary != null)
                        {
                            this.SystemUnitClassMappingRole = mappingRoleClassLibrary.RoleClass.First;
                        }
                    }
                    else
                    {
                        return false;
                    }
                }
                else
                {
                    return false;
                }
            }
            else if (this.SystemUnitClassMappingRole == null)
            {
                var lib =
                    this.AmlDocumentRepresentation.CaexObject.RoleClassLib.GetCaexObjectByName("MappingRoleClassLib") as
                    RoleClassLibType;
                if (lib != null)
                {
                    this.SystemUnitClassMappingRole = lib.RoleClass.First;
                }
            }

            // add the MappingSystemUnitClassLib
            if ( this.AmlDocumentRepresentation.CaexObject.SystemUnitClassLib.FindCaexObjectByName("MappingSystemUnitClassLib") == null)
            {
                var classLibDocument =
                    CAEXDocument.LoadFromStream(
                        assembly.GetManifestResourceStream(
                            "AutomationMLMapper.AutomationML.MappingLibraryResources.MappingSystemUnitClassLib.aml"));
                if (classLibDocument != null)
                {
                    var lib =
                        classLibDocument.CAEXFile.SystemUnitClassLib.GetCaexObjectByName("MappingSystemUnitClassLib");
                    if (lib is SystemUnitClassLibType)
                    {
                        var systemUnitClassMappingLibrary =
                            this.AmlDocumentRepresentation.CaexObject.Import_SystemUnitClassLibHierarchy(
                                lib as SystemUnitClassLibType);
                        if (systemUnitClassMappingLibrary != null)
                        {
                            this.SystemUnitClassMappingObject = systemUnitClassMappingLibrary.SystemUnitClass.First;
                        }
                    }
                    else
                    {
                        return false;
                    }
                }
                else
                {
                    return false;
                }
            }
            else if (this.SystemUnitClassMappingObject == null)
            {
                var lib =
                    this.AmlDocumentRepresentation.CaexObject.SystemUnitClassLib.GetCaexObjectByName(
                        "MappingSystemUnitClassLib") as SystemUnitClassLibType;
                if (lib != null)
                {
                    this.SystemUnitClassMappingObject = lib.SystemUnitClass.First;
                }
            }

            // propose a name for the MappingClassLibrary if it is not there and add it to the AmlDocument
            if (this.MappingClassLibrary == null)
            {
                if (this.ExportInstancesCollection.Elements.Count > 0)
                {
                    var sref = this.ExportInstancesCollection.Elements[0].CaexObject.GetRefBaseSystemUnitPath();
                    if (!string.IsNullOrEmpty(sref))
                    {
                        var pathComps = sref.Split('/');
                        if (pathComps.Length > 0)
                        {
                            var mappingClassLibraryName = pathComps[0] + "Mapping";
                            this.MappingClassLibrary =
                                this.AmlDocumentRepresentation.CaexObject.SystemUnitClassLib.GetCaexObjectByName(
                                    mappingClassLibraryName) as SystemUnitClassLibType
                                ??
                                this.AmlDocumentRepresentation.CaexObject.New_SystemUnitClassLibHierarchy(
                                    mappingClassLibraryName);
                        }
                    }
                }
            }

            return true;
        }

        /// <summary>
        /// Test if the <see cref="LoadIHCommand"/> - Command can execute
        /// </summary>
        /// <param name="parameter">
        /// The parameter. 
        /// </param>
        /// <returns>
        /// The System.Boolean. 
        /// </returns>
        private static bool LoadIHCommandCanExecute(object parameter)
        {
            return true;
        }

        /// <summary>
        ///   For all Internal Elements Class-to-Class Mappings are generated for those, which are marked for mapping
        /// </summary>
        private void CreateMappingClassesForImport()
        {
            var mappedWithClassToClassMapping = new List<ImportInternalElementViewModel>();

            foreach (var systemUnitClass in this.ClassesCollection.Elements)
            {
                foreach (var internalElement in systemUnitClass.ImportedInstancesCollection.Elements)
                {
                    // create a Mapping Class if the element is marked (Checked)
                    if (internalElement.MapToClass)
                    {
                        var internalElementFromImportData =
                            this.ExportInstancesCollection.Elements.FirstOrDefault(
                                ie => ie.CaexObject.ID.Value == internalElement.CaexObject.ID.Value);
                        if (internalElementFromImportData != null && internalElementFromImportData.CaexObject != null)
                        {
                            // create a class-to-class mapping, source class information is derived from the internal element
                            var mappingClass =
                                ClassToClassMapping.CreateMappingClass(
                                    internalElementFromImportData.CaexObject.GetRefBaseSystemUnitPath(), 
                                    this.MappingClassLibrary);

                            // write target class information to the mapping object
                            if (mappingClass != null)
                            {
                                ClassToClassMapping.AssignTargetClassToMappingObject(
                                    mappingClass, 
                                    systemUnitClass.CaexObject, 
                                    this.SystemUnitClassMappingObject, 
                                    this.SystemUnitClassMappingRole);

                                mappedWithClassToClassMapping.Add(internalElement);
                            }
                        }
                    }
                }
            }

            // delete those instances, which are not mapped to classes although there is a classToClass Mapping
            foreach (var systemUnitClass in this.ClassesCollection.Elements)
            {
                systemUnitClass.DeleteInstancesNotMappedToClass(mappedWithClassToClassMapping);
            }
        }

        /// <summary>
        ///   For all Internal Elements Class-to-Class Mappings are generated for those, which are marked for mapping
        /// </summary>
        private void GetMappincClassesForImport()
        {
            foreach (var systemUnitClass in this.ClassesCollection.Elements)
            {
                foreach (var internalElement in systemUnitClass.ImportedInstancesCollection.Elements)
                {
                    var internalElementFromImportData =
                        this.ExportInstancesCollection.Elements.FirstOrDefault(
                            ie => ie.CaexObject.ID.Value == internalElement.CaexObject.ID.Value);
                    if (internalElementFromImportData != null && internalElementFromImportData.CaexObject != null)
                    {
                        // create a class-to-class mapping, source class information is derived from the internal element
                        var mappingClass =
                            ClassToClassMapping.GetMappingClass(
                                internalElementFromImportData.CaexObject.GetRefBaseSystemUnitPath(), 
                                this.MappingClassLibrary);

                        // write target class information to the mapping object
                        if (mappingClass != null)
                        {
                            var mappingObject = ClassToClassMapping.GetClassToTargetMappingObject(
                                mappingClass, systemUnitClass.CaexObject);
                            internalElement.MapToClass = mappingObject != null;
                        }
                    }
                }
            }
        }
        
        /// <summary>
        /// The Execute Action of the <see cref="LoadIHCommand"/> - Command
        /// </summary>
        /// <param name="parameter">
        /// The parameter. 
        /// </param>
        private void LoadIHCommandExecute(object parameter)
        {
            var ofd = new OpenFileDialog
                {
                    Filter = Resources.AutomationMLFileFilter, 
                    Title = Resources.AutomationMLFileContainingIH, 
                    DefaultExt = "aml"
                };
            var result = ofd.ShowDialog();
            if (result.Value)
            {
                this.ExportInstancesCollection.Elements.Clear();
                this.ReadAutomationMLExportData(ofd.FileName);
            }
        }

        /// <summary>
        /// Test if the <see cref="MapIECommand"/> - Command can execute.  
        /// The <see cref="ExportInstancesCollection"/> and <see cref="SystemViewModel.ClassesCollection"/> should contain elements
        /// </summary>
        /// <param name="parameter">
        /// The parameter. 
        /// </param>
        /// <returns>
        /// true, if executable. 
        /// </returns>
        private bool MapIECommandCanExecute(object parameter)
        {
            return this.ExportInstancesCollection.Elements.Count > 0 && this.ClassesCollection.Elements.Count > 0;
        }

        /// <summary>
        /// The Execute Action of the <see cref="MapIECommand"/> - Command
        /// </summary>
        /// <param name="parameter">
        /// The parameter. 
        /// </param>
        private void MapIECommandExecute(object parameter)
        {
            var mappedElements = new Dictionary<InternalElementViewModel, int>();

            // Loop over all InternalELements in the Import Document to find direct Mappings
            foreach (var importedElement in this.ExportInstancesCollection.Elements)
            {
                mappedElements.Add(importedElement, 0);
                foreach (var classToMap in this.ClassesCollection.Elements)
                {
                    classToMap.IsExpanded = true;
                    if (importedElement.CaexObject.InternalElementDirectlyMappsToSystemUnitClass(classToMap.CaexObject))
                    {
                        mappedElements[importedElement]++;

                        classToMap.CreateInstanceFromImportedElement(importedElement.CaexObject);
                    }
                }
            }

            // if there is any element left, which couldn't mapped on any SystemUnitClass directly
            if (mappedElements.Values.Any(v => v == 0))
            {
                // TupleData
                // Item1 = Generation of the Matching Role in the Ancestor Tree
                // Item2 = The SystemUnitClass
                // Item3 = The SupportedRoleClass which is mapped to the Ancestor of the required ROLE of the Imported Element
                var unmappedElements =
                    new Dictionary<InternalElementViewModel, List<Tuple<int, SystemUnitClassViewModel, SupportedRoleClassType>>>();

                foreach (var mappedElement in mappedElements)
                {
                    if (mappedElement.Value == 0)
                    {
                        var importedElement = mappedElement.Key;

                        unmappedElements.Add(
                            importedElement, new List<Tuple<int, SystemUnitClassViewModel, SupportedRoleClassType>>());

                        foreach (var classToMap in this.ClassesCollection.Elements)
                        {
                            SupportedRoleClassType matchingRole;

                            // get the Generation of a matching Ancestor in the RoleHierarchy of the Required Role
                            var generation =
                                mappedElement.Key.CaexObject.InternalElementMappsToSystemUnitClassWithAncestorRole(
                                    classToMap.CaexObject, out matchingRole);

                            // if a match is found and there is not any match so far with a generation lower than this in my List
                            if (generation > 0 && matchingRole != null
                                && !unmappedElements[importedElement].Any(l => l.Item1 < generation))
                            {
                                // delete all Elements, with a generation greater than the actual one
                                unmappedElements[importedElement].RemoveAll(p => p.Item1 > generation);

                                // add this
                                unmappedElements[importedElement].Add(
                                    new Tuple<int, SystemUnitClassViewModel, SupportedRoleClassType>(
                                        generation, classToMap, matchingRole));
                            }
                        }
                    }
                }

                foreach (var unmappedElement in unmappedElements)
                {
                    var importedElement = unmappedElement.Key;
                    foreach (var mappingTuple in unmappedElement.Value)
                    {
                        mappingTuple.Item2.CreateInstanceFromImportedElement(
                            importedElement.CaexObject, mappingTuple.Item3);
                        mappedElements[importedElement]++;
                    }
                }
            }

            if (this.AddMappingLibraries())
            {
                this.GetMappincClassesForImport();
            }

            this.ReValidate();
        }

        /// <summary>
        /// Test if <see cref="MapIECommand"/> - Command can execute. 
        /// The <see cref="SystemViewModel.ClassesCollection"/> and <see cref="ExportInstancesCollection"/> should contain elements.
        /// </summary>
        /// <param name="parameter">
        /// The parameter (unused).
        /// </param>
        /// <returns>
        /// The System.Boolean. 
        /// </returns>
        private bool MapIEToClassCommandCanExecute(object parameter)
        {
            return this.ExportInstancesCollection.Elements.Count > 0 && this.ClassesCollection.Elements.Count > 0;
        }

        /// <summary>
        /// The Execute Action of the <see cref="MapIEToClassCommand"/> - Command
        /// </summary>
        /// <param name="parameter">
        /// The parameter (unuesed). 
        /// </param>
        private void MapIEToClassCommandExecute(object parameter)
        {
            if (this.AddMappingLibraries())
            {
                this.CreateMappingClassesForImport();
                this.ReValidate();
            }
        }

        /// <summary>
        /// Read the AutomationML File and extract the InternalElement Data from the InstanceHierarchy
        /// </summary>
        /// <param name="filename">
        /// The filename. 
        /// </param>
        private void ReadAutomationMLExportData(string filename)
        {
            if (File.Exists(filename))
            {
                var importAmlDocument = this.LoadAndMerge(filename);

                foreach (InstanceHierarchyType lib in importAmlDocument.CAEXFile.InstanceHierarchy)
                {
                    foreach (var internalElementRepresenation in from InternalElementType ie in lib.InternalElement
                                                                 select
                                                                     new ExportInternalElementViewModel(ie)
                                                                         {
                                                                             IsValid = true, ImportState = ImportStateEnum.Unused 
                        })
                    {
                        internalElementRepresenation.LoadCaexToRepresentationModel();
                        this.ExportInstancesCollection.Elements.Add(internalElementRepresenation);
                    }
                }
            }
        }

        #endregion
    }
}